说出来可能不信,我以前写的文章全都搞丢了 😭。 hexo 生成的项目没有好好保存,电脑 recovery 之后就丢失了之前项目以及 post 的 md 文件 …

现在的 blog 好空啊 … 趁空闲写一篇设计模式 - 策略模式的文章充充数吧

模式动机

为了达到一个目的,我们可以有多种方法去实现,选择什么样的策略或者策略组合可以根据环境或者条件让使用者去自由选择。

eg:

表单验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function validate (value, type) {
switch (type){
case 'username':
if (!value) {
console.log('用户名不能为空')
} else if (value.length < 6) {
console.log('用户名的长度不能小于 6 位')
} else {
console.log(`用户名 - ${value}`)
break
}
case 'pwd':
if (!value) {
console.log('密码不能为空')
} else if (value.length !== 4 || value.length !== 6) {
console.log('密码长度必须为 4 位 或者 6 位')
} else {
console.log(`密码 - ${value}`)
break
}
default:
console.error('无对应类型验证')
}
}
// 用户名验证
var username = document.querySelector('#username').value
validate(username, 'username')
// 密码验证
var pwd = document.querySelector('#password').value
validate(pwd, 'pwd')

对于上例中,调用者的出发点是获得一个 Input 输入框输入值,系统给用户输出的是输入值的回馈,在这个过程中有许多方法可以用于验证用户输入的值。而这些方法在不经过设计的情况下,就是一大段一大段的 if...else 混杂着业务逻辑充斥着我们的代码,于是策略模式正是基于这样的场景下诞生的。

模式定义

定义一系列算法,将每一个算法包装起来,并让它们可以互相替换。

属于行为型模式。

具体实现

关于策略模式的 UML 图和时序图,可以参考下面两图,来自于 Graphic Design Patterns :

从 UML 图我们可以看到我们需要构建的的类结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 策略抽象类
class Strategy {
constructor () {}
excute () {
...
}
}
// 策略 A
class ConcreteStrategyA extends Strategy {
constructor () {
super()
}
excute () {
...
}
}
// 策略 B
class ConcreteStrategyB extends Strategy {
constructor () {
super()
}
excute () {
...
}
}
// 执行类
class Context {
constructor () {
this.strategies = []
}
excute () {
for (let strategy of strategies) {
strategy.excute()
}
}
setStrategy (strategy) {
if (strategy instanceof Strategy) {
this.strategies.push(strategy)
} else {
console.error('非策略对象!')
}
}
}

这是传统的面向对象范式下构造出来的类结构,其实在 JS 中我们没必要如此麻烦的 …

从时序图我们可以得到具体执行的代码结构

1
2
3
4
5
6
7
8
9
function main () {
var context = new Context()
var strategyA = new ConcreteStrategyA()
var strategyB = new ConcreteStrategyB()
context.setStrategy(strategyA)
context.excute()
context.setStrategy(strategyB)
context.excute()
}

从最初用 swtich...case 加上 if...else 实现的代码,到现在经过策略模式设计后的代码我们可以清晰看到差别有以下几点:

  • 对于使用者而言,使用哪种策略他是非常清晰的,但是他并不需要真的了解策略的具体实现。因此我们解耦了策略的调用 & 策略的实现两个过程。
  • 对于新增策略,使用者不需要去改动执行过程的代码,只需要新增一个自己想要的策略类即可。
  • 对于策略组合的使用也更加灵活,可以一个一个策略去验证,也可以组合好一组策略然后再去执行。

案例

以表单验证为案例,实现策略模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/**
* [表单策略组]
* @type {Object}
*/
let formStrategies = {
/**
* [空值判断]
* @param {[Any]} val [校验值]
* @param {[String]} msg [错误信息]
*/
isEmpty: (val, msg) => {
if (!val) {
return msg
}
},
/**
* [特定值判断,严格判断]
* @param {[String]} val [校验值]
* @param {[String]} assignedVal [对比值]
* @param {[String]} msg [错误信息]
*/
eqAssignedVal: (val, assignedVal, msg) => {
if (val !== assignedVal) {
return msg
}
},
/**
* [最小值判断,限 Number]
* @param {[String]} val [校验值]
* @param {[String]} min [对比值]
* @param {[String]} msg [错误信息]
*/
ltMinVal: (val, min, msg) => {
let parseVal = parseFloat(val)
let parseMin = parseFloat(min)
if (isNaN(parseVal) || isNaN(parseMin) || parseVal < parseMin) {
return msg
}
},
/**
* [最大值判断,限 Number]
* @param {[String]} val [校验值]
* @param {[String]} max [对比值]
* @param {[String]} msg [错误信息]
*/
gtMaxVal: (val, max, msg) => {
let parseVal = parseFloat(val)
let parseMax = parseFloat(max)
if (isNaN(parseVal) || isNaN(parseMax) || parseVal > parseMax) {
return msg
}
},
/**
* [手机号格式判断]
* @param {[String]} val [校验值]
* @param {[String]} msg [错误信息]
*/
isMobile: (val, msg) => {
if (!(/^1[34578]\d{9}$/.test(val))) {
return msg
}
},
/**
* [验证码格式校验]
* @param {[String]} val [校验值]
* @param {[String]} msg [错误信息]
*/
isSms: (val, msg) => {
if (!/^\d{6}$/.test(val)) {
return msg
}
},
/**
* [验证值是否为整百数据格式]
* @param {[String]} val [校验值]
* @param {[String]} msg [错误信息]
*/
isHundredInterge: (val, msg) => {
if (!/^\w+00$/.test(val)) {
return msg
}
},
/**
* [验证值是否是整数]
* @param {[Number]} val [校验值]
* @param {[String]} msg [错误信息]
*/
isInterge: (val, msg) => {
if (val % 1 !== 0) {
return msg
}
}
}
/**
* 表单策略组执行类
*/
class FormValidate {
/*
* cache 验证规则缓存
*/
constructor () {
this.cache = []
}
/**
* [添加验证规则]
* @param {[String]} val [校验值]
* @param {[Array]} rules [验证规则,策略组中必须存在该规则]
*/
add (val, rules) {
for (let rule of rules) {
let strategyAry = rule.strategy.split(':')
let msg = rule.msg
this.cache.push(() => {
let strategy = strategyAry.shift()
strategyAry.unshift(val)
strategyAry.push(msg)
return formStrategies[strategy].apply(this, strategyAry)
})
}
}
/**
* [验证规则延迟执行]
*/
start () {
for (let validateFunc of this.cache) {
let msg = validateFunc()
if (msg) {
return msg
}
}
}
}
export default FormValidate
// 具体调用实现
let fv = new FormValidate()
let username = document.querySelector('#username').value
let pwd = document.querySelector('#password').value
fv.add(username, [{
strategy: 'isEmpty',
msg: '手机号不能为空'
}, {
strategy: 'isMobile',
msg: '手机号格式不正确'
}])
fv.add(password, [{
strategy: 'ltMinVal:6',
msg: '密码长度不能少于 6 位'
}, {
strategy: 'gtMaxVal:20',
msg: '密码长度不能多于 20 位'
}])
let msg = fv.start()
if (msg) {
console.error(msg)
} else {
...
}

以上是我现在在公司项目对整站做的一个表单策略验证的封装。

Java 实现:

1
2
3
public interface Strategy {
public String doOperation();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class JudgyNullValue implements Strategy {
String val;
String msg;
public JudgyNullValue(String val, String msg) {
this.val = val;
this.msg = msg;
}
public String doOperation() {
return this.val == "" ? this.msg : "";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.ArrayList;
import com.strategy.Strategy;
public class Context {
ArrayList<Strategy> strategies = new ArrayList<Strategy>();
public void addStrategy(Strategy strategy) {
this.strategies.add(strategy);
}
public String excuteStrategies() {
String resultMsg = "";
for (Strategy strategy: strategies) {
resultMsg = strategy.doOperation();
if (resultMsg != "") {
return resultMsg;
}
}
return "";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import com.context.Context;
import com.strategy.JudgyNullValue;
public class Main {
public static void main(String[] args) {
Context context = new Context();
JudgyNullValue judgyNullValue = new JudgyNullValue("", "字符串不能为空");
context.addStrategy(judgyNullValue);
String result = context.excuteStrategies();
if (result != "") {
System.out.println(result);
} else {
System.out.println("There is no error");
}
}
}

图解策略模式